package p2pcdnsim.cdn;

import java.util.HashMap;
import java.util.Iterator;

import desmoj.core.simulator.SimTime;

public class LFUCaching<T extends ObjectContent> implements CachingStrategy<T> {
	
	private HashMap<Long,Entry> objects;
	private HashMap<Long,Entry> rejectedObjects;
	private long cacheSize;
	private long currBytes;

	public LFUCaching() {
		this.objects = new HashMap<Long,Entry>();
		this.rejectedObjects = new HashMap<Long,Entry>();
		this.currBytes = 0;
	}
	
	
	public void setCacheSize(long size) {
		this.cacheSize = size;
	}

	@Override
	public T retrieveObject(long objectId, SimTime timeStamp) {
		Entry entry = null;
		boolean hit = false;
		boolean isObject = false;
		boolean inCache = true; 
		
		if(this.objects.get(objectId) != null){
			hit = true;
			isObject = true;
		}else if(this.rejectedObjects.get(objectId)!= null){
			hit = true;
		}
		
		if(hit && isObject){
			entry = this.objects.get(objectId);
		}else if(hit && !isObject){
			entry = this.rejectedObjects.get(objectId);
			inCache = false;
		}else{
			return null;
		}
		
		entry.frequency++;//Updates the frequency
		
		if(!isObject && isPossible(entry)){
			long temp = this.rejectedObjects.get(objectId).content.getLength();
			
			//while there is not enough space, remove Last Frequently Used
			while((this.currBytes + temp > this.cacheSize)) removeLFU();
			
			this.currBytes += temp;
			this.objects.put(objectId, entry);
			this.rejectedObjects.remove(objectId);
			
		}
		
		if(!inCache)return null;
		
		return entry.content;
	}

	@Override
	public boolean insertObject(T content, SimTime currentTime) {
		
		//If there is not sufficient space to this object, do not try to cache
		if(content.getLength() > this.cacheSize) return false;

		Entry entry;
		entry = new Entry();
		entry.content = content;
		entry.frequency = 1;
		this.rejectedObjects.put(content.getObjectId(), entry);
		

		if(this.currBytes + content.getLength() <= this.cacheSize){
			this.rejectedObjects.remove(content.getObjectId());
			this.objects.put(content.getObjectId(), entry);
			this.currBytes += content.getLength();
		}
		
		return true;
	}

	//It verify if is possible put this object in the cache
	private boolean isPossible(Entry entry) {
		long currBytesTemp = this.currBytes;
		Iterator<Entry> it = objects.values().iterator();
		Entry e;
		
		while((currBytesTemp + entry.content.getLength()) > cacheSize && it.hasNext()){
			e = it.next();
			if(e.frequency < entry.frequency){
				currBytesTemp = currBytesTemp - e.content.getLength();
			}
		}
		
		if((this.cacheSize - currBytesTemp) >= entry.content.getLength()) return true;
		return false;
	}

	// Ineficiente
	private void removeLFU() {
		Iterator<Entry> it = objects.values().iterator();
		Entry e = it.next();
		ObjectContent content = e.content;
		int leastFrequency = e.frequency;
		while(it.hasNext()){
			e = it.next();
			if(e.frequency < leastFrequency) {
				leastFrequency = e.frequency;
				content = e.content;
			}
		}
		
		this.rejectedObjects.put(content.getObjectId(),this.objects.get(content.getObjectId()));
		this.objects.remove(content.getObjectId());
		this.currBytes = this.currBytes - content.getLength();
	}
	
	
	private class Entry {	
		public int frequency;
		public T content;
	}

}
